#include "StdAfx.h"
#include "SkySphere.h"
#include "M2Manager.h"

D3DManager* Singleton<D3DManager>::m_instance = 0;
//M2Cache* Singleton<M2Cache>::m_instance = 0;

D3DManager::D3DManager(CWindow* pWindow) : m_pWindow(pWindow)
{
	m_appState = STATE_MENU;
	m_device = 0;
	m_milliseconds = 0;
	WINDOWINFO inf;
	GetWindowInfo(*pWindow, &inf);
	ZeroMemory(&m_params, sizeof(D3DPRESENT_PARAMETERS));
	m_params.AutoDepthStencilFormat = D3DFMT_D16;
	m_params.BackBufferFormat = D3DFMT_A8R8G8B8;
	m_params.BackBufferHeight = inf.rcClient.bottom - inf.rcClient.top;
	m_params.BackBufferWidth = inf.rcClient.right - inf.rcClient.left;
	m_params.EnableAutoDepthStencil = TRUE;
	m_params.hDeviceWindow = *m_pWindow;
	m_params.Windowed = TRUE;
	m_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
	m_params.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;

	m_driver = Direct3DCreate9(D3D_SDK_VERSION);
	if(!m_driver)
	{
		StartupException s;
		s.szTitle = "DirectX - Error";
		s.szBody = "Could not create IDirect3D9 (Drect3DCreate9(D3D_SDK_VERSION) returned 0\nMake sure you have DirectX 9.0c installed!";
		throw s;
	}
	HRESULT hRes = m_driver->CreateDevice(0, D3DDEVTYPE_HAL, *m_pWindow, D3DCREATE_HARDWARE_VERTEXPROCESSING, &m_params, &m_device);
	if(FAILED(hRes))
	{
		char msg[255];
		sprintf_s<255>(msg, "Could not create IDirect3DDevice9. Errorcode: %u. Contact Devs to get more information!", HRESULT_CODE(hRes));
		StartupException s;
		s.szTitle = "DirectX - Error";
		s.szBody = msg;
		throw s;
	}

	checkCaps();

	CTextureHandler::GetInstance()->GetTexture("Interface\\Buttons\\UI-Panel-Button-Up.blp"); // precaching of some often used textures
	CTextureHandler::GetInstance()->GetTexture("Interface\\Buttons\\UI-Panel-Button-Down.blp");
	CTextureHandler::GetInstance()->GetTexture("Interface\\COMMON\\Common-Input-Border.blp");

	D3DCAPS9 caps;
	m_device->GetDeviceCaps(&caps);

	m_deviceLost = false;

	ui16 verMin = caps.PixelShaderVersion & 0xFF;
	ui16 verMax = (caps.PixelShaderVersion >> 8) & 0xFF;

	if(verMax < 3)
	{
		StartupException s;
		s.szTitle = "DirectX - Error";
		s.szBody = "Your device is not supporting Shader 3.0!";
		throw s;
	}

	m_lastUpdate = GetTickCount();
	m_renders = 0;
	new ShaderManager(m_device);
	new Camera();
	new UIManager();
	new TutorialHandler();
	new SelectionMgr();
	sUI.Init();
	new LoadingScreen();
	new VertexCache();
	new Agreement();
	new TerrainManager();
	m_cursor = CTextureHandler::GetInstance()->GetTexture("Interface\\Cursor\\Point.blp");
	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovRH(&matProj, D3DXToRadian(45), (float)m_params.BackBufferWidth / (float)m_params.BackBufferHeight, 1.0f, 2.6f * TILESIZE);
	m_device->SetTransform(D3DTS_PROJECTION, &matProj);
	m_matProj = matProj;

	mSkySphere = new SkySphere(530.0f);
	new M2Manager();

	m_device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	m_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	m_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

	m_device->SetRenderState(D3DRS_LIGHTING, FALSE);
	m_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); 
}

void D3DManager::onDeviceLost() {
	sUI.BeforeReset();
	sMainMenu.BeforeReset();
	sShaderMgr.onDeviceLost();
}

void D3DManager::resetDevice() {
	HRESULT hRes = m_device->Reset(&m_params);
	if(FAILED(hRes)) {
		ui32 res = MessageBox(*m_pWindow, "There was an error reseting the DirectX device. The program is unable to recover from this error "\
			"and has to be terminated. Do you like to save all the changes that are not yet saved?", "Error", MB_YESNO);
		if(res == IDYES) {
			Save();
		}

		ExitProcess(0);
	}

	sUI.OnReset();
	sMainMenu.OnReset();
	sShaderMgr.onDeviceReset();
}

void D3DManager::ChangeSize()
{
	if(!m_device)
		return;

	bool resetState = false;
	if(sD3D.GetAppState() == STATE_WORLD)
	{
		resetState = true;
		sD3D.SetAppState(STATE_LOADING);
		sD3D.Render();
	}

	m_renderLock.Acquire();
	WINDOWINFO inf;
	GetWindowInfo(*m_pWindow, &inf);
	m_params.BackBufferHeight = inf.rcClient.bottom - inf.rcClient.top;
	m_params.BackBufferWidth = inf.rcClient.right - inf.rcClient.left;

	onDeviceLost();
	resetDevice();

	sSelection.Resize();

	m_renderLock.Release();

	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovRH(&matProj, D3DXToRadian(45), (float)m_params.BackBufferWidth / (float)m_params.BackBufferHeight, 1.0f, 2.6f * TILESIZE);
	m_device->SetTransform(D3DTS_PROJECTION, &matProj);
	m_device->SetTransform(D3DTS_VIEW, &sCamera.GetMatView());
	m_matProj = matProj;

	if(resetState)
	{
		sD3D.SetAppState(STATE_WORLD);
		sD3D.Render();
	}
}

static ui32 gSeconds = 0;
static ui32 gLastTime = 0;
static bool m_av = false;
static bool m_iz = false;

void D3DManager::Render()
{
	if(!gLastTime)
		gLastTime = GetTickCount();
	else
	{
		m_milliseconds += GetTickCount() - gLastTime;
		gLastTime = GetTickCount();
	}
	++m_renders;
	float fps = 0;
	ui32 now = GetTickCount();
	if(now - m_lastUpdate >= 1000)
	{
		fps = (float)m_renders;
		m_lastUpdate = now;
		m_renders = 0;
		++gSeconds;
		char szTitle[1024];
		memset(szTitle, 0, 1024);
		sprintf_s<1024>(szTitle, "YiasEdit - DirectX 9.0c - FPS: %.02f", fps);
		SetWindowText(*m_pWindow, szTitle);
	}
	if(!m_device)
		return;

	
	m_renderLock.Acquire();
	m_device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
	m_device->BeginScene();

	if(m_appState != STATE_WORLD)
		sCamera.Render();

	if(m_appState == STATE_WORLD)
	{
		mTaskMgr.executeFrame();
		m_meshMutex.Acquire();

		std::for_each(m_adts.begin(), m_adts.end(),
			[](std::pair<ui32, ADTModel*> val) 
			{ 
				if(val.second != NULL)
					val.second->Update(0);
			}
		);

		sCamera.Render();

		mSkySphere->render();

		m_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); 

		std::for_each(m_adts.begin(), m_adts.end(),
			[](std::pair<ui32, ADTModel*> val)
			{
				if(val.second != NULL)
					val.second->Draw(sCamera.GetCamPosition(), sCamera.GetTargetPosition());
			}
		);

		/*std::set<ModelBase*>::iterator itr = m_meshes.begin();
		std::set<ModelBase*>::iterator end = m_meshes.end();


		for( ; itr != end; ++itr)
		{
				(*itr)->Draw(sCamera.GetCamPosition(), sCamera.GetTargetPosition());
		}*/
		sM2Mgr.render();
		m_meshMutex.Release();

		sSelection.Render();
	}

	sUI.Render();
	sLoading.Render();
	//sBrowsing.Render2D();
	sSelection.update();

	m_device->EndScene();
	m_device->Present(0, 0, 0, 0);
	//sBrowsing.Render3D();

	m_modfRendered.clear();
	m_renderLock.Release();
}

void D3DManager::RemoveAdt(ui32 iX, ui32 iZ)
{
	ui32 combi = iX * 100 + iZ;
	if(!m_adts[combi])
		return;
	m_meshMutex.Acquire();
	ADTModel* mod = m_adts[combi];
	m_changeLock.Acquire();
	if(m_changed.find(mod) != m_changed.end())
	{
		mod->Save();
		m_changed.erase(mod);
	}
	m_changeLock.Release();
	m_adts.erase(combi);
	mod->Deinit();
	m_meshes.erase(mod);
	delete mod;
	m_meshMutex.Release();
}

Vertex3F D3DManager::GetWorldCoords(ui32 mouseX, ui32 mouseY)
{
	D3DXVECTOR3 v, rayDir, rayOrigin;
	D3DXMATRIX mat, m, matWorld, matView;

	sD3D->GetTransform(D3DTS_PROJECTION, &mat);
	sD3D->GetTransform(D3DTS_VIEW, &matView);
	sD3D->GetTransform(D3DTS_WORLD, &matWorld);
	D3DVIEWPORT9 viewPort;
	m_device->GetViewport(&viewPort);

	D3DXVECTOR3 vecEnd, vecStart;
	D3DXVec3Unproject(&vecStart, &D3DXVECTOR3(mouseX, mouseY, 0), &viewPort, &mat, &matView, &matWorld);
	D3DXVec3Unproject(&vecEnd, &D3DXVECTOR3(mouseX, mouseY, 1), &viewPort, &mat, &matView, &matWorld);

	rayOrigin = vecStart;
	rayDir = *D3DXVec3Normalize(&rayDir, &(vecEnd - vecStart));

	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();
	
	float pDistance = 0.0f;
	float curDistance = 999999.0f;
	Vertex3F retPoint(-99999.0f, -99999.0f, -99999.0f);

	m_renderLock.Acquire();

	for( ; itr != end; ++itr)
	{
		if(itr->second && itr->second->Intersect(rayOrigin, rayDir, &pDistance))
		{
			if(pDistance < curDistance)
			{
				D3DXVECTOR3 t = rayDir;
				D3DXVec3Normalize(&t, &rayDir);
				D3DXVECTOR3 tmp = t * pDistance;
				D3DXVECTOR3 r = rayOrigin + tmp;
				retPoint = Vertex3F(r.x, r.y, r.z);
				curDistance = pDistance;
			}
		}
	}

	m_renderLock.Release();

	return retPoint;
}

void D3DManager::OnMouseClick(Vertex2F pos, bool up, bool left)
{
	if(m_appState != STATE_WORLD)
		return;
	if(!left)
		return;
	if(!up)
		return;

	sSelection.SetClickPoint(GetWorldCoords(pos.x, pos.y));
}

void D3DManager::ChangeHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->ChangeHeight(pos, radius, amount, type);
	}
}

void D3DManager::ChangeTexture(Vertex3F pos, float radius, float amount, ui32 type, Texture* tex, ui32 brushType)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->TextureTerrain(pos, radius, amount, type, tex, brushType);
	}
}

void D3DManager::FlattenHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->FlattenHeight(pos, radius, amount, type);
	}
}

void D3DManager::BlurHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->BlurHeight(pos, radius, amount, type);
	}
}

void D3DManager::ChangeMCCV(ui8 a, ui8 r, ui8 g, ui8 b, Vertex3F point, float radius)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->ChangeMCCV(a, r, g, b, point, radius);
	}
}

unsigned long __stdcall WdlProc(LPVOID)
{
	MsgBox b(400, 100, 8, 3, "WDL", "Do you want to save the WDL?");
	ExitThread(0);
}

void D3DManager::Save()
{
	m_changeLock.Acquire();

	std::set<ADTModel*>::iterator itr = m_changed.begin();
	std::set<ADTModel*>::iterator end = m_changed.end();

	Log::Print("Saving %u ADT...\n", m_changed.size());

	for( ; itr != end; ++itr)
		(*itr)->Save();
	char msg[255];
	sprintf_s<255>(msg, "Saved %u ADT", m_changed.size());
	sHud.SetText(msg, 7500, 0xFFFF7F00);
	m_changed.clear();
	m_changeLock.Release();
}

void D3DManager::RedoNormals() {
	m_changeLock.Acquire();

	std::set<ADTModel*>::iterator itr = m_changed.begin();
	std::set<ADTModel*>::iterator end = m_changed.end();

	for( ; itr != end; ++itr)
		(*itr)->RedoNormals();

	m_changeLock.Release();
}

float D3DManager::GetLandHeight(float x, float z)
{
	float oldX = x;
	float oldZ = z;
	ui32 indexX = (ui32)(x / TILESIZE);
	ui32 indexZ = (ui32)(z / TILESIZE);
	ADTModel* m = GetADT(indexX, indexZ);
	if(!m)
		return 65536.0f;
	x -= indexX * TILESIZE;
	z -= indexZ * TILESIZE;
	indexX = (ui32)(x / CHUNKSIZE);
	indexZ = (ui32)(z / CHUNKSIZE);
	ADTChunk* c = m->GetChunk(indexX, indexZ);
	if(!c)
		return 65536.0f;
	float h = c->GetLandHeight(oldX, oldZ);
	return h;
}

bool D3DManager::getLandHeightFast(float x, float z, float& h) {
	float oldX = x;
	float oldZ = z;
	ui32 indexX = (ui32)(x / TILESIZE);
	ui32 indexZ = (ui32)(z / TILESIZE);
	ADTModel* m = GetADT(indexX, indexZ);
	if(!m)
		return false;
	x -= indexX * TILESIZE;
	z -= indexZ * TILESIZE;
	indexX = (ui32)(x / CHUNKSIZE);
	indexZ = (ui32)(z / CHUNKSIZE);
	ADTChunk* c = m->GetChunk(indexX, indexZ);
	if(!c)
		return false;
	return c->getLandHeightFast(oldX, oldZ, h);
}

void D3DManager::ScreenShot()
{
	IDirect3DSurface9* pSurface = 0;
	IDirect3DTexture9* pTexture = 0;
	HWND hDesktop = GetDesktopWindow();
	WINDOWINFO inf;
	GetWindowInfo(hDesktop, &inf);
	ui32 sizeX = inf.rcClient.right - inf.rcClient.left;
	ui32 sizeY = inf.rcClient.bottom - inf.rcClient.top;
	m_device->CreateTexture(sizeX, sizeY, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pTexture, 0);
	pTexture->GetSurfaceLevel(0, &pSurface);
	if(FAILED(m_device->GetFrontBufferData(0, pSurface)))
	{
		MessageBoxA(0, "Failed!", "", MB_OK);
		return;
	}
	D3DXSaveSurfaceToFileA("Screenshot.jpg", D3DXIFF_JPG, pSurface, 0, 0);
}

void D3DManager::OnClose()
{
	if(!m_changed.size())
		return;
	else
	{
		int res = MessageBox(*m_pWindow, "There are unsaved ADT. Would you like to save first?", "Warning", MB_YESNO);
		if(res == IDYES)
			Save();
	}
}

void D3DManager::ToggleHole(Vertex3F pos)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->ToggleHole(pos);
	}
}

void D3DManager::DrawShadow(Vertex3F pos, float radius, bool on)
{
	std::map<ui32, ADTModel*>::iterator itr = m_adts.begin();
	std::map<ui32, ADTModel*>::iterator end = m_adts.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
			itr->second->DrawShadow(pos, radius, on);
	}
}

void D3DManager::AddMesh(ModelBase* pMesh)
{
	switch(pMesh->GetType())
	{
	case 0:
		m_mdxModels.insert((M2Model*)pMesh);
		break;
	}
	m_meshes.insert(pMesh);
}

void D3DManager::RemoveMesh(ModelBase* pMesh)
{
	switch(pMesh->GetType())
	{
	case 0:
		m_mdxModels.erase((M2Model*)pMesh);
		break;
	}
	m_meshes.erase(pMesh);
}

void D3DManager::UpdateMDXPosition(ADTModel* pBaseModel, ui32 doodad, float x, float y, float z)
{

}

bool D3DManager::ShouldRender(MODF& mdf)
{
	for(ui32 i = 0; i < m_modfRendered.size(); ++i)
	{
		MODF& m = m_modfRendered[i];
		if(m.uniqueId != mdf.uniqueId)
			continue;

		float dx = mdf.position[0] - m.position[0];
		float dy = mdf.position[1] - m.position[1];
		float dz = mdf.position[2] - m.position[2];

		float dist = sqrtf(dx * dx + dy * dy + dz * dz);
		if(dist < CHUNKSIZE / 2.0f)
			return false;
	}

	m_modfRendered.push_back(mdf);
	return true;
}

#define CAPS_FLAG(cap, flag) \
	if((cap & flag) != 0)\
		Log::Print("%s ", #flag)

void D3DManager::checkCaps() {
	D3DCAPS9 caps;
	m_device->GetDeviceCaps(&caps);

	D3DADAPTER_IDENTIFIER9 adapter;
	m_driver->GetAdapterIdentifier(0, NULL, &adapter);

	Log::Print("========================================================================\n");
	Log::Print("|	Information for adapter: %s\n", adapter.Description);
	Log::Print("========================================================================\n");
	Log::Print("Pixel shader: v%u.%u\n", (caps.PixelShaderVersion >> 8) & 0xFF, caps.PixelShaderVersion & 0xFF);
	Log::Print("Vertex shader: v%u.%u\n", (caps.VertexShaderVersion >> 8) & 0xFF, caps.VertexShaderVersion & 0xFF);
	Log::Print("MIN_FILTER: ");
	CAPS_FLAG(caps.TextureFilterCaps, D3DPTFILTERCAPS_MIPFPOINT);
	CAPS_FLAG(caps.TextureFilterCaps, D3DPTFILTERCAPS_MIPFLINEAR);
	Log::Print("\n");
}